﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Looptribe.Moodlin8.Model.Response;
using Windows.Storage;
using Windows.Storage.Streams;

namespace Looptribe.Moodlin8.Model.Api
{
    public class Moodle241ApiService : IMoodleApiService
    {
        protected HttpClient HttpClient { get; private set; }
        private string _token;
        public string Token { get { return _token; } }

        public bool IsLogged { get { return HttpClient != null; } }

        public const string CachedFilesFolder = "CachedFiles";

        public void Logout()
        {
            _token = null;
        }

        public async Task<IResponse<string>> AuthenticateAsync(ServiceCredentials credentials)
        {
            if (HttpClient != null)
            {
                HttpClient.Dispose();
                HttpClient = null;
            }
            HttpClient = new HttpClient
                              {
                                  BaseAddress = credentials.BaseUri
                              };
            var response = await HttpClient.GetAsync(String.Format("login/token.php?username={0}&password={1}&service={2}",
                WebUtility.UrlEncode(credentials.Username), WebUtility.UrlEncode(credentials.Password), WebUtility.UrlEncode(credentials.ServiceName)));
            var tokenResponse = new MoodleJsonResponse<TokenResponse>(response, await response.Content.ReadAsStringAsync());
            if (tokenResponse.Status == Status.Ok && tokenResponse.Data.Token != null)
            {
                _token = tokenResponse.Data.Token;
            }
            else
            {
                _token = null;
                HttpClient.Dispose();
                HttpClient = null;
            }
            return new MoodleJsonResponse<string>(tokenResponse.Status, _token);
        }

        public async Task<IResponse<IEnumerable<Course>>> GetCoursesAsync()
        {
            var response = await HttpClient.GetAsync(GenerateFunctionUrl("core_course_get_courses").ToString());
            return new MoodleJsonResponse<IEnumerable<Course>>(response, await response.Content.ReadAsStringAsync());
        }

        public async Task<IResponse<IEnumerable<Course>>> GetEnrolledCoursesAsync(User user)
        {
            var url = GenerateFunctionUrl("core_enrol_get_users_courses");
            url.Append("&userid=" + user.Id);
            var response = await HttpClient.GetAsync(url.ToString());
            return new MoodleJsonResponse<IEnumerable<Course>>(response, await response.Content.ReadAsStringAsync());
        }

        public async Task<IResponse<IEnumerable<CourseContent>>> GetCourseContentsAsync(long id)
        {
            var url = GenerateFunctionUrl("core_course_get_contents");
            url.Append("&courseid=" + id);
            var response = await HttpClient.GetAsync(url.ToString());
            return new MoodleJsonResponse<IEnumerable<CourseContent>>(response, await response.Content.ReadAsStringAsync());
        }

        public async Task<IResponse<IEnumerable<CourseUser>>> GetCourseEnrolledUsersAsync(long id)
        {
            var url = GenerateFunctionUrl("core_enrol_get_enrolled_users");
            url.Append("&courseid=" + id);
            var response = await HttpClient.GetAsync(url.ToString());
            return new MoodleJsonResponse<IEnumerable<CourseUser>>(response, await response.Content.ReadAsStringAsync());
        }

        public async Task<IResponse<WebserviceInfo>> GetWebserviceInfo()
        {
            var url = GenerateFunctionUrl("core_webservice_get_site_info");
            var response = await HttpClient.GetAsync(url.ToString());
            return new MoodleJsonResponse<WebserviceInfo>(response, await response.Content.ReadAsStringAsync());
        }

        public async Task<IResponse<IEnumerable<CourseUser>>> GetContactsAsync()
        {
            var url = GenerateFunctionUrl("local_moodlin8_get_contacts");
            var response = await HttpClient.GetAsync(url.ToString());
            return new MoodleJsonResponse<IEnumerable<CourseUser>>(response, await response.Content.ReadAsStringAsync());
        }

        public async Task<IResponse<FileCompound>> GetFilesAsync(string filepath = null)
        {
            var url = GenerateFunctionUrl("local_moodlin8_get_files");
            if (!String.IsNullOrEmpty(filepath))
                url.Append("&filepath=" + filepath);
            var response = await HttpClient.GetAsync(url.ToString());
            return new MoodleJsonResponse<FileCompound>(response, await response.Content.ReadAsStringAsync());
        }

        public async Task<IResponse<File>> UploadFileAsync(IStorageFile file)
        {
            var url = GenerateFunctionUrl("local_moodlin8_upload_file");
            var postData = new List<KeyValuePair<string, string>>
                               {
                                   new KeyValuePair<string, string>("component", "user"),
                                   new KeyValuePair<string, string>("filearea", "private"),
                                   new KeyValuePair<string, string>("itemid", "0"),
                                   new KeyValuePair<string, string>("filepath", "/"),
                                   new KeyValuePair<string, string>("filename", file.Name),
                                   new KeyValuePair<string, string>("filecontent", await StorageFileToBase64(file))
                               };
            HttpResponseMessage response;
            using (var content = new MultipartFormDataContent())
            {
                foreach (var kvp in postData)
                    content.Add(new StringContent(kvp.Value), kvp.Key);
                response = await HttpClient.PostAsync(url.ToString(), content);
            }
            var body = await response.Content.ReadAsStringAsync();
            return new MoodleJsonResponse<File>(response, body);
        }

        public async Task<IResponse<IStorageFile>> DownloadFileAsync(File file)
        {
            var url = GenerateFunctionUrl("local_moodlin8_download_file");
            url.Append("&filename=");
            url.Append(file.FileName);
            if (!String.IsNullOrEmpty(file.FilePath))
                url.Append("&filepath=" + file.FilePath);
            var response = await HttpClient.GetAsync(url.ToString());
            var obj = new MoodleJsonResponse<DownloadFileResponse>(response, await response.Content.ReadAsStringAsync());
            if (obj.Status != Status.Ok || String.IsNullOrEmpty(obj.Data.File))
                return new ObjectResponse<IStorageFile>(Status.Error, null);
            var folder = await ApplicationData.Current.TemporaryFolder.CreateFolderAsync(CachedFilesFolder, CreationCollisionOption.OpenIfExists);
            var outFile = await folder.CreateFileAsync(file.FileName, CreationCollisionOption.ReplaceExisting);
            using (var randomAccessStream = await outFile.OpenAsync(FileAccessMode.ReadWrite))
            using (var stream = randomAccessStream.GetOutputStreamAt(0))
            using (var writer = new DataWriter(stream))
            {
                writer.WriteBytes(Convert.FromBase64String(obj.Data.File));
                await writer.StoreAsync();
                writer.DetachStream();
                await randomAccessStream.FlushAsync();
            }
            return new ObjectResponse<IStorageFile>(Status.Ok, outFile);
        }

        public async Task<IResponse<IEnumerable<Assignment>>> GetAssignmentsAsync(IEnumerable<long> courseIds)
        {
            var url = GenerateFunctionUrl("local_moodlin8_get_assignments");
            if (courseIds != null)
                foreach (var id in courseIds)
                {
                    url.Append("&courseids[]=");
                    url.Append(id);
                }
            var response = await HttpClient.GetAsync(url.ToString());
            var wrapped = new MoodleJsonResponse<Moodling8GetAssignmentsResponse>(response, await response.Content.ReadAsStringAsync());
            if (wrapped.Status != Status.Ok)
                return new MoodleJsonResponse<IEnumerable<Assignment>>(wrapped.Status, null);
            return new MoodleJsonResponse<IEnumerable<Assignment>>(wrapped.Status, wrapped.Data.Courses.SelectMany(a => a.Assignments));
        }

        public async Task<IResponse<IEnumerable<Assignment>>> GetAssignmentsAsync(long courseId)
        {
            return await GetAssignmentsAsync(new[] { courseId });
        }

        public async Task<IResponse<IEnumerable<InstantMessage>>> GetMessagesAsync()
        {
            var url = GenerateFunctionUrl("local_moodlin8_get_messages");
            var response = await HttpClient.GetAsync(url.ToString());
            return new MoodleJsonResponse<IEnumerable<InstantMessage>>(response, await response.Content.ReadAsStringAsync());
        }

        public async Task<IResponse<IEnumerable<InstantMessage>>> GetMessagesAsync(long userid)
        {
            var url = GenerateFunctionUrl("local_moodlin8_get_conversation");
            url.Append("&userid=");
            url.Append(userid);
            var response = await HttpClient.GetAsync(url.ToString());
            return new MoodleJsonResponse<IEnumerable<InstantMessage>>(response, await response.Content.ReadAsStringAsync());
        }

        public async Task<IResponse<InstantMessage>> SendMessageAsync(long userId, string message)
        {
            var url = GenerateFunctionUrl("core_message_send_instant_messages");
            var postData = new List<KeyValuePair<string, string>>
                               {
                                   new KeyValuePair<string, string>("messages[0][touserid]", userId.ToString()),
                                   new KeyValuePair<string, string>("messages[0][text]", message),
                                   new KeyValuePair<string, string>("messages[0][textformat]", "2"),
                                   new KeyValuePair<string, string>("messages[0][clientmsgid]", DateTime.Now.Ticks.ToString())
                               };
            var response = await HttpClient.PostAsync(url.ToString(), new FormUrlEncodedContent(postData));
            var wrapped = new MoodleJsonResponse<IEnumerable<InstantMessage>>(response, await response.Content.ReadAsStringAsync());
            return new ObjectResponse<InstantMessage>(wrapped.Status, wrapped.Data.FirstOrDefault());
        }

        public async Task<IResponse<IEnumerable<Event>>> GetEventsAsync(DateTime start, DateTime end)
        {
            var url = GenerateFunctionUrl("local_moodlin8_get_events");
            url.Append("&start=");
            url.Append(DateTimeToTimestamp(start));
            url.Append("&end=");
            url.Append(DateTimeToTimestamp(end));
            var response = await HttpClient.GetAsync(url.ToString());
            return new MoodleJsonResponse<IEnumerable<Event>>(response, await response.Content.ReadAsStringAsync());
        }

        public async Task<IResponse<IStorageFile>> DownloadFileAsync(string uri)
        {
            var request = new HttpRequestMessage(HttpMethod.Get, uri);
            var response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
            if (!response.IsSuccessStatusCode)
                return new ObjectResponse<IStorageFile>(Status.Error, null);
            if (response.Content.Headers.ContentDisposition == null)
                return new ObjectResponse<IStorageFile>(Status.Error, null);
            var suggestedFilename = response.Content.Headers.ContentDisposition.FileName;
            if (String.IsNullOrEmpty(suggestedFilename))
                return new ObjectResponse<IStorageFile>(Status.Error, null);
            suggestedFilename = suggestedFilename.Trim(new [] {'"'});
            var folder = await ApplicationData.Current.TemporaryFolder.CreateFolderAsync(CachedFilesFolder, CreationCollisionOption.OpenIfExists);
            var file = await folder.CreateFileAsync(suggestedFilename, CreationCollisionOption.ReplaceExisting);
            using (var randomAccessStream = await file.OpenAsync(FileAccessMode.ReadWrite))
            using (var stream = randomAccessStream.GetOutputStreamAt(0))
            using (var writer = new DataWriter(stream))
            {
                writer.WriteBytes(await response.Content.ReadAsByteArrayAsync());
                await writer.StoreAsync();
                writer.DetachStream();
                await randomAccessStream.FlushAsync();
            }
            return new ObjectResponse<IStorageFile>(Status.Ok, file);
        }

        public string GetDownloadUrl(ModuleContent content)
        {
            if (!content.Type.Equals(ModuleContent.TypeFile))
                return content.FileUrl;
            return content.FileUrl + "&token=" + Token;
        }

        private long DateTimeToTimestamp(DateTime date)
        {
            var epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime();
            return (long)(date - epoch).TotalSeconds;
        }

        private async Task<string> StorageFileToBase64(IStorageFile file)
        {
            using (var stream = await file.OpenAsync(FileAccessMode.Read))
            using (var reader = new DataReader(stream.GetInputStreamAt(0)))
            {
                await reader.LoadAsync((uint) stream.Size);
                var bytes = new byte[stream.Size];
                reader.ReadBytes(bytes);
                return Convert.ToBase64String(bytes);
            }
        }

        private StringBuilder GenerateFunctionUrl(string function)
        {
            if (String.IsNullOrEmpty(Token))
                throw new InvalidOperationException("Cannot generate function URL without a login token.");
            var builder = new StringBuilder("webservice/rest/server.php");
            builder.Append("?wstoken=");
            builder.Append(WebUtility.UrlEncode(Token));
            builder.Append("&wsfunction=");
            builder.Append(WebUtility.UrlEncode(function));
            builder.Append("&moodlewsrestformat=json");
            return builder;
        }
    }
}
